from neo4j.exceptions import ServiceUnavailable
import logging
from GlobalVar import globalVar
import os
import json
import re


class NodeStorer:
    # 存储节点需要考虑miss_pkg，给这些没找到的也都建一个节点
    def __init__(self, db_driver, batch_size):
        self.driver = db_driver
        self.batch_size = batch_size
        self.session = self.driver.session()
        self.tx = self.session.begin_transaction()
        self.finished_pkg_node = []
        self.license_set = {'UNLICENSED': 0, 'Unlicense': 0,
                            'AGPL-3.0': 5,
                            'GPL-2.0': 4, 'GPL-3.0': 4, 'EPL-2.0': 4, 'MPL-2.0': 4,
                            'LGPL-3.0': 3, 'LGPL-2.1': 3,
                            'Apache-2.0': 2, 'CC0-1.0': 2, 'BSD-3-Clause': 2, 'BSD-2-Clause': 2, 'MIT': 2,
                            'Apache2': 2,
                            'Plubic Domain': 1
                            }
        logging.basicConfig(filename='store_node.log', level=logging.INFO,
                            format='%(asctime)s %(levelname)s: %(message)s')

    def db_commit(self, commitNow=False):
        globalVar.order_num += 1
        if commitNow or globalVar.order_num >= self.batch_size:
            self.tx.commit()
            globalVar.order_num = 0
            self.session.close()
            self.session = self.driver.session()
            self.tx = self.session.begin_transaction()

            os.makedirs('./finished_pkg_node_logs', exist_ok=True)
            globalVar.finished_pkg_node.extend(self.finished_pkg_node)
            globalVar.save_list(globalVar.finished_pkg_node,
                                f"./finished_pkg_node_logs/finished_pkg_node_{len(globalVar.finished_pkg_node)}"
                                )
            if self.finished_pkg_node != [] and self.finished_pkg_node is not None:
                logging.info(f'finish store allV node: {self.finished_pkg_node[-1]}')
            self.finished_pkg_node = []


    def search_license_level(self, license):
        if license in self.license_set.keys():
            return self.license_set[license]
        else:
            return 1

    def find_maxl(self, l):
        max_level = -1
        for license in l:
            cur_level = self.search_license_level(license)
            if cur_level > max_level:
                max_level = cur_level
                max_license = license
        return max_license, max_level

    def find_minl(self, l):
        min_level = 10
        for license in l:
            cur_level = self.search_license_level(license)
            if cur_level < min_level:
                min_level = cur_level
                min_license = license
        return min_license, min_level

    def single_license(self, license_str):
        # print(type(license_str))
        # if isinstance(license_str, list):
        #     print(license_str)

        if isinstance(license_str, dict):
            if 'type' in license_str.keys():
                license = license_str['type']
            elif 'name' in license_str.keys():
                license = license_str['name']
            else:
                license = 'UNKNOWN'
            return license, self.search_license_level(license)
        else:
            if 'SEE LICENSE IN' in license_str:
                return 'UNKNOWN', self.search_license_level('UNKNOWN')
            elif 'with' in license_str:
                license = re.split(r'-with-', license_str)[0]
                if license == '' or license == None:
                    license = 'UNKNOWN'
                return license, self.search_license_level(license)
            elif 'License, Version' in license_str:
                str_ = re.split(r'License, Version', license_str)
                license = str_[0].strip()
                if len(str_) >= 2:
                    license += '-' + str_[1].strip()
                return license, self.search_license_level(license)
            elif '+' in license_str:
                license = license_str.rstrip('+')
                m = re.match(r"^([a-zA-Z]+)([0-9]+\.[0-9]+)$", license)
                str = 'UNKNOWN'
                if m != None:
                    str = m.group(1) + '-' + m.group(2)
                return license, self.search_license_level(str)
            else:
                if license_str == None:
                    return 'UNKNOWN', self.search_license_level('UNKNOWN')
                license = license_str.strip("(")
                license = license.strip(")")
                l = license.replace(" ", "")
                l = l.replace('or', 'OR')
                l = l.replace('Or', 'OR')
                l = l.replace('and', 'AND')
                l = l.replace('And', 'AND')
                if 'OR' in l:
                    l = re.split(r'OR', l)
                    min_license, min_level = self.find_minl(l)
                    return license, min_level
                elif 'AND' in l:
                    l = re.split(r'AND', l)
                    max_license, max_level = self.find_maxl(l)
                    return license, max_level
                elif '/' in l:
                    l = re.split(r'/', l)
                    min_license, min_level = self.find_minl(l)
                    return license, min_level
                else:
                    license = '' if license_str == None else license_str
                    return license, self.search_license_level(license)

    def handle_license(self, license_info, version):
        if 'license' in license_info.keys():
            if isinstance(license_info['license'], list):
                # 佛了，license字段还可能是list, list里还可能是dict
                max_level = -1
                max_license = ''
                for license in license_info['license']:
                    cur_license, cur_level = self.single_license(license)
                    if cur_level > max_level:
                        max_level = cur_level
                        max_license = cur_license
                return  max_license, max_level
            else:
                return self.single_license(license_info['license'])

        elif 'licenses' in license_info.keys():
            max_level = -1
            max_license = ''
            for license in license_info['licenses']:
                if isinstance(license, list):
                    for sub_license in license:
                        cur_license, cur_level = self.single_license(sub_license)
                        if cur_level > max_level:
                            max_level = cur_level
                            max_license = cur_license
                else:
                    cur_license, cur_level = self.single_license(license)
                    if cur_level > max_level:
                        max_level = cur_level
                        max_license = cur_license
            return max_license, max_level
        else:
            return 'UNDECLARED', 0

    def store_miss_pkg(self, pkg_name):
        pkg_id = pkg_name
        version = ""
        query = (
            "MERGE (p1:Pkg { id: $pkg_id }) SET p1.name = $pkg_name, p1.version = $version"
        )
        result = self.tx.run(query, pkg_id=pkg_id, pkg_name=pkg_name, version=version)
        self.db_commit()

    # 存储一个节点，一个版本到数据库
    def store_pkg(self, pkg_json, pkg_name, version):
        if not pkg_name or not version or not pkg_json:
            print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")
            print(pkg_name)
            print(version)
            print("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++")

        if version == '':
            pkg_id = pkg_name
            pkg_time = pkg_json['time']['created'] if 'time' in pkg_json.keys() and 'created' in pkg_json[
                'time'].keys() else 'Unknown'
        else:
            pkg_id = pkg_name + '@' + version
            pkg_time = pkg_json['time'][version] if 'time' in pkg_json.keys() and version in pkg_json[
                'time'].keys() else 'Unknown'

        ver_info = pkg_json
        if 'versions' in pkg_json.keys() and version in pkg_json['versions'].keys():
            ver_info = pkg_json['versions'][version]
            pkg_id = ver_info['_id'] if '_id' in ver_info.keys() else pkg_name + '@' + version

        pkg_license, license_level = self.handle_license(ver_info, version)


        pkg_description = ver_info['description'] if 'description' in ver_info.keys() else 'None'
        pkg_homepage = ver_info['homepage'] if 'homepage' in ver_info.keys() else 'Unknown'

        query = (
            "MERGE (p1:Pkg{ id: $pkg_id }) SET p1.name = $pkg_name, p1.version = $version, p1.time = $pkg_time, p1.description = $pkg_description, p1.license = $pkg_license, p1.homepage = $pkg_homepage, p1.allV = True "
            "MERGE (p2: License{name: $pkg_license, level: $license_level}) "
            "MERGE (p1)-[use:Use]->(p2) "
            "RETURN p1"
        )
        result = self.tx.run(query, pkg_id=pkg_id, pkg_name=pkg_name, version=version, pkg_time=str(pkg_time),
                        pkg_description=str(pkg_description), pkg_license=str(pkg_license), pkg_homepage=str(pkg_homepage),
                        license_level=license_level)
        self.db_commit()

        # try:
        #     return [{"pkg ": record["p1"]["id"]}
        #             for record in result]
        # # Capture any errors along with the query and data for traceability
        # except ServiceUnavailable as exception:
        #     logging.error("CREATE BASIC- {query} raised an error: \n {exception}".format(
        #         query=query, exception=exception))
        #     raise

    # 存储一个节点，所有版本
    def store_allver_pkg(self, pkg_name, pkg_json):
        if "versions" not in pkg_json:
            self.store_pkg(pkg_json, pkg_name, "")
            self.tx.run(
                f"MERGE (p1:Pkg{{name: '{pkg_name}', id: '{pkg_name}@all', "
                f"singleV: True, allV: True, vnum: 1}}) RETURN p1.id"
            )

            self.db_commit()
            return

        versions = pkg_json['versions']
        for ver in versions.keys():
            self.store_pkg(pkg_json, pkg_name, ver)
        # 怎么存储那个allV属性？还有全版本遍历后要生成一个@ALL节点吗
        self.tx.run(f"MERGE (p1:Pkg{{name: '{pkg_name}', id: '{pkg_name}@all', "
                    f"allV: True, vnum: {len(versions.keys())}}}) RETURN p1.id")
        self.db_commit()

    def store_all(self, continueStoreNode=False):
        file_list = os.listdir("./projects")
        if continueStoreNode:
            # 之前有已完成的，要排除掉这些

            finish_len = len(globalVar.finished_pkg_node)
            file_list = file_list[finish_len:]

        else:
            # 先把所有的miss_pkg存一下
            for pkg_name in globalVar.miss_pkg:
                self.store_miss_pkg(pkg_name)

        for filename in file_list:
            with open("./projects/"+filename, 'r', encoding="utf-8") as f:
                pkg_json = json.load(f)
                if '_id' in pkg_json:
                    pkg_name = pkg_json['_id']

                    self.store_allver_pkg(pkg_name, pkg_json)
                    self.finished_pkg_node.append(pkg_name)
                    print(f'finish store allV node: {pkg_name}')
                    # logging.info(f'finish store allV node: {pkg_name}')
                else:
                    continue

        self.db_commit(True)







